Windows 11 で MFA 認証を使ってスイッチロール(AssumeRole)し Terraform を実行する
コーヒーが好きな emi です。
「別の AWS アカウントにスイッチロールして terraform
コマンドを実行したいな、でも provider
に profile
の設定を書きたくないな」
と思っていたところ、以下ブログが公開されました。これで解決できそうです。
早速手元の Windows 11 で試してみましたので、コマンドを紹介します。
前提
- 端末は Windows 11
- Git for Windows インストール済
- AWS CLI インストール済
- Terraform インストール済 *1 *2
- Visual Studio Code(以降 VSCode と省略)インストール済
- VSCode に Terraform 拡張導入済
- VSCode で
git
コマンド、AWS CLI コマンド、terraform
コマンドが利用できる状態になっている - VSCode のターミナルは PowerShell
- この後のコマンドは VSCode のターミナルの PowerShell で実行する
AWS CLI の設定情報
Windows で AWS CLI をデフォルトでインストールした場合、AWS CLI をインストールしたユーザーのホームディレクトリの「.aws」の配下(C:\Users\ユーザー\.aws
)に、設定ファイル(config)と認証情報ファイル(credentials)があります。
# source AWS account [default] region = ap-northeast-1 output = json # destination AWS account IAM role : kitani.emi [profile kitani.emi] region = ap-northeast-1 output = json role_arn = arn:aws:iam::スイッチ先AWSアカウントID:role/kitani.emi source_profile = default mfa_serial = arn:aws:iam::スイッチ元AWSアカウントID:mfa/kitani.emi # Get credentials for terraform [profile kitani.emi-tf] credential_process = aws configure export-credentials --profile kitani.emi # Can be passed in the environment variable AWS_PROFILE instead of stating it on the tf file side.
上記 config ファイルでは 3 つのプロファイルを設定しています。
[default]
プロファイル- 標準の AWS CLI 設定で、特定のプロファイルを指定しない場合に使用される
[profile kitani.emi]
- スイッチ先 AWS アカウントの IAM ロール(
kitani.emi
)にスイッチするためのプロファイル
- スイッチ先 AWS アカウントの IAM ロール(
[profile kitani.emi-tf]
credential_process
を使用しkitani.emi
プロファイルを通じて得られる認証情報を自動的に取得- これを利用すると Terraform が AWS リソースにアクセスできる
# source AWS account [default] aws_access_key_id = YOUR_ACCESS_KEY aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
環境変数の設定
以下のコマンドでスイッチ先 AWS アカウントの IAM ロールのプロファイル [profile kitani.emi]
の認証情報を確認します。MFA 認証をしていない場合、途中 MFA の 6 桁のコードの入力を求められます。
aws configure export-credentials --profile kitani.emi
▼実行結果
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> aws configure export-credentials --profile kitani.emi Enter MFA code for arn:aws:iam::スイッチ元AWSアカウントID:mfa/cm-kitani.emi: # ここで MFA の 6 桁のコードを入力する { "Version": 1, "AccessKeyId": "ASXXXXXXXXXXXXXXXXXX", "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "SessionToken": "XXXXXXXXXX~~~XXXXXXXXXX", "Expiration": "2024-04-23T01:13:07+00:00" } PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
export-credentials
は指定されたプロファイルの認証情報を JSON 形式で標準出力に出力します。上記のようにコマンドが正常に実行され、認証情報が出力されれば正常にプロファイル設定できています。
この [profile kitani.emi]
の認証情報を [profile kitani.emi-tf]
として保存し、Terraform 実行時に利用するというわけです。
VSCode のターミナル(PowerShell)で以下コマンドを実行し、環境変数で profile を設定します。
$env:AWS_PROFILE="kitani.emi-tf"
▼実行結果例
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> $env:AWS_PROFILE="kitani.emi-tf" PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
これにより、現在のセッションで指定したプロファイルが Terraform によって使用されます。
Terraform コマンドの実行
環境変数を設定した後、通常通り Terraform コマンドを実行します。
今回は例として Terraform でパスと IP を指定して AWS WAF のルールグループを動的に生成する | DevelopersIO で使用した以下のコードをローカルに git clone
し、S3 バケットや tfstate ファイルの情報を指定して terraform コマンドで展開しました。
環境変数 AWS_PROFILE
でプロファイル kitani.emi-tf
が設定されているため、Terraform はこのプロファイルを使用して AWS リソースにアクセスしています。
terraform init
実行コマンド
terraform init
▼実行結果
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> terraform init Initializing the backend... Enter MFA code for arn:aws:iam::スイッチ元AWSアカウントID:mfa/kitani.emi: Initializing modules... Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Using previously-installed hashicorp/aws v5.46.0 Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
terraform plan
長いので結果はトグル内にしまっていますが、terraform コマンドの実行は成功しています。
実行コマンド
terraform plan
実行結果(クリックで展開)
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> terraform plan Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.waf.aws_cloudwatch_log_group.cwlogs will be created + resource "aws_cloudwatch_log_group" "cwlogs" { + arn = (known after apply) + id = (known after apply) + log_group_class = (known after apply) + name = "aws-waf-logs-alb-webacl" + name_prefix = (known after apply) + retention_in_days = 30 + skip_destroy = false + tags = { + "Name" = "aws-waf-logs-alb-webacl" } + tags_all = { + "Name" = "aws-waf-logs-alb-webacl" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample2-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample3-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample2-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample2-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample2/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample3-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample3-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample3/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl.web_acl will be created + resource "aws_wafv2_web_acl" "web_acl" { + application_integration_url = (known after apply) + arn = (known after apply) + capacity = (known after apply) + description = "alb-wabacl" + id = (known after apply) + lock_token = (known after apply) + name = "alb-webacl" + scope = "REGIONAL" + tags = { + "Name" = "alb-webacl" } + tags_all = { + "Name" = "alb-webacl" } + default_action { + allow { } } + rule { + name = "Access-Ristrict-Sample2PathIpRestriction" + priority = 1001 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample2PathIpRestriction" + sampled_requests_enabled = true } } + rule { + name = "Access-Ristrict-Sample3PathIpRestriction" + priority = 1002 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample3PathIpRestriction" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "alb-webacl" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl_association.waf_association will be created + resource "aws_wafv2_web_acl_association" "waf_association" { + id = (known after apply) + resource_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxxxxxxxx" + web_acl_arn = (known after apply) } # module.waf.aws_wafv2_web_acl_logging_configuration.waflog will be created + resource "aws_wafv2_web_acl_logging_configuration" "waflog" { + id = (known after apply) + log_destination_configs = (known after apply) + resource_arn = (known after apply) + logging_filter { + default_behavior = "DROP" + filter { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition { + action_condition { + action = "BLOCK" } } + condition { + action_condition { + action = "CAPTCHA" } } + condition { + action_condition { + action = "COUNT" } } } } } Plan: 8 to add, 0 to change, 0 to destroy. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
terraform apply(途中 yes 入力)
実行コマンド
terraform apply
実行結果(クリックで展開)
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.waf.aws_cloudwatch_log_group.cwlogs will be created + resource "aws_cloudwatch_log_group" "cwlogs" { + arn = (known after apply) + id = (known after apply) + log_group_class = (known after apply) + name = "aws-waf-logs-alb-webacl" + name_prefix = (known after apply) + retention_in_days = 30 + skip_destroy = false + tags = { + "Name" = "aws-waf-logs-alb-webacl" } + tags_all = { + "Name" = "aws-waf-logs-alb-webacl" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample2-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample3-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample2-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample2-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample2/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample3-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample3-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample3/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl.web_acl will be created + resource "aws_wafv2_web_acl" "web_acl" { + application_integration_url = (known after apply) + arn = (known after apply) + capacity = (known after apply) + description = "alb-wabacl" + id = (known after apply) + lock_token = (known after apply) + name = "alb-webacl" + scope = "REGIONAL" + tags = { + "Name" = "alb-webacl" } + tags_all = { + "Name" = "alb-webacl" } + default_action { + allow { } } + rule { + name = "Access-Ristrict-Sample2PathIpRestriction" + priority = 1001 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample2PathIpRestriction" + sampled_requests_enabled = true } } + rule { + name = "Access-Ristrict-Sample3PathIpRestriction" + priority = 1002 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample3PathIpRestriction" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "alb-webacl" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl_association.waf_association will be created + resource "aws_wafv2_web_acl_association" "waf_association" { + id = (known after apply) + resource_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx" + web_acl_arn = (known after apply) } # module.waf.aws_wafv2_web_acl_logging_configuration.waflog will be created + resource "aws_wafv2_web_acl_logging_configuration" "waflog" { + id = (known after apply) + log_destination_configs = (known after apply) + resource_arn = (known after apply) + logging_filter { + default_behavior = "DROP" + filter { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition { + action_condition { + action = "BLOCK" } } + condition { + action_condition { + action = "CAPTCHA" } } + condition { + action_condition { + action = "COUNT" } } } } } Plan: 8 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Destroying... [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/cfe7ea2e-b3a7-4aae-8a92-0a034a9b23d1] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Destruction complete after 0s module.waf.aws_cloudwatch_log_group.cwlogs: Destroying... [id=aws-waf-logs-alb-webacl] module.waf.aws_cloudwatch_log_group.cwlogs: Destruction complete after 1s module.waf.aws_wafv2_web_acl.web_acl: Destroying... [id=cfe7ea2e-b3a7-4aae-8a92-0a034a9b23d1] module.waf.aws_wafv2_web_acl.web_acl: Destruction complete after 1s module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Destroying... [id=72cf5e5c-f461-43d8-98ea-462c86e2434c] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Destroying... [id=530e0cf6-aa12-46d7-bdaa-118a6139d09b] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Destruction complete after 0s module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Destruction complete after 3s module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Destroying... [id=cb701822-9175-447c-9072-d742c1b5d864] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Destroying... [id=ad542e96-ab31-46e3-b998-1b2bca116592] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Destruction complete after 0s module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Destruction complete after 3s Destroy complete! Resources: 7 destroyed. PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.waf.aws_cloudwatch_log_group.cwlogs will be created + resource "aws_cloudwatch_log_group" "cwlogs" { + arn = (known after apply) + id = (known after apply) + log_group_class = (known after apply) + name = "aws-waf-logs-alb-webacl" + name_prefix = (known after apply) + retention_in_days = 30 + skip_destroy = false + tags = { + "Name" = "aws-waf-logs-alb-webacl" } + tags_all = { + "Name" = "aws-waf-logs-alb-webacl" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample2-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample2-ristrict-ipsets" } } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample3-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample3-ristrict-ipsets" } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample2-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample2-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample2-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample2/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample2-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample3-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample3-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample3-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample3/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample3-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl.web_acl will be created + resource "aws_wafv2_web_acl" "web_acl" { + application_integration_url = (known after apply) + arn = (known after apply) + capacity = (known after apply) + description = "alb-wabacl" + id = (known after apply) + lock_token = (known after apply) + name = "alb-webacl" + scope = "REGIONAL" + tags = { + "Name" = "alb-webacl" } + tags_all = { + "Name" = "alb-webacl" } + default_action { + allow { } } + rule { + name = "Access-Ristrict-Sample2PathIpRestriction" + priority = 1001 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample2PathIpRestriction" + sampled_requests_enabled = true } } + rule { + name = "Access-Ristrict-Sample3PathIpRestriction" + priority = 1002 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample3PathIpRestriction" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "alb-webacl" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl_association.waf_association will be created + resource "aws_wafv2_web_acl_association" "waf_association" { + id = (known after apply) + resource_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx" + web_acl_arn = (known after apply) } # module.waf.aws_wafv2_web_acl_logging_configuration.waflog will be created + resource "aws_wafv2_web_acl_logging_configuration" "waflog" { + id = (known after apply) + log_destination_configs = (known after apply) + resource_arn = (known after apply) + logging_filter { + default_behavior = "DROP" + filter { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition { + action_condition { + action = "BLOCK" } } + condition { + action_condition { + action = "CAPTCHA" } } + condition { + action_condition { + action = "COUNT" } } } } } Plan: 8 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Creating... module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Creating... module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Creation complete after 1s [id=xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Creation complete after 1s [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Creating... module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Creating... module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Creation complete after 1s [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Creation complete after 1s [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Creating... module.waf.aws_wafv2_web_acl.web_acl: Creation complete after 1s [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Creating... module.waf.aws_cloudwatch_log_group.cwlogs: Creating... module.waf.aws_cloudwatch_log_group.cwlogs: Creation complete after 0s [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Creating... module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Creation complete after 0s [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Still creating... [10s elapsed] module.waf.aws_wafv2_web_acl_association.waf_association: Still creating... [20s elapsed] module.waf.aws_wafv2_web_acl_association.waf_association: Still creating... [30s elapsed] module.waf.aws_wafv2_web_acl_association.waf_association: Creation complete after 34s [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx] Apply complete! Resources: 8 added, 0 changed, 0 destroyed. PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
terraform destroy
terraform destroy
実行結果(クリックで展開)
PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf> terraform destroy module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # module.waf.aws_cloudwatch_log_group.cwlogs will be destroyed - resource "aws_cloudwatch_log_group" "cwlogs" { - arn = "arn:aws:logs:ap-northeast-1:スイッチ先AWSアカウントID:log-group:aws-waf-logs-alb-webacl" -> null - id = "aws-waf-logs-alb-webacl" -> null - log_group_class = "STANDARD" -> null - name = "aws-waf-logs-alb-webacl" -> null - retention_in_days = 30 -> null - skip_destroy = false -> null - tags = { - "Name" = "aws-waf-logs-alb-webacl" } -> null - tags_all = { - "Name" = "aws-waf-logs-alb-webacl" } -> null } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be destroyed - resource "aws_wafv2_ip_set" "access_ristrict" { - addresses = [ - "0.0.0.0/1", - "128.0.0.0/1", ] -> null - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/ipset/access-ristrict-sample2-ristrict-ipsets/xxxxxxxxxx" -> null - description = "access-ristrict-sample2-ristrict-ipsets" -> null - id = "xxxxxxxxxx" -> null - ip_address_version = "IPV4" -> null - lock_token = "f5b71e81-73ef-44ef-bfe3-219fbb40537a" -> null - name = "access-ristrict-sample2-ristrict-ipsets" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "access-ristrict-sample2-ristrict-ipsets" } -> null - tags_all = { - "Name" = "access-ristrict-sample2-ristrict-ipsets" } -> null } # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"] will be destroyed - resource "aws_wafv2_ip_set" "access_ristrict" { - addresses = [ - "0.0.0.0/1", - "128.0.0.0/1", ] -> null - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/ipset/access-ristrict-sample3-ristrict-ipsets/xxxxxxxxxx" -> null - description = "access-ristrict-sample3-ristrict-ipsets" -> null - id = "xxxxxxxxxx" -> null - ip_address_version = "IPV4" -> null - lock_token = "c7d96fbe-7963-494a-b98e-6322f9f5bdcc" -> null - name = "access-ristrict-sample3-ristrict-ipsets" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "access-ristrict-sample3-ristrict-ipsets" } -> null - tags_all = { - "Name" = "access-ristrict-sample3-ristrict-ipsets" } -> null } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"] will be destroyed - resource "aws_wafv2_rule_group" "access_ristrict" { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/rulegroup/access-ristrict-sample2-ristrict-waf-rulegp/xxxxxxxxxx" -> null - capacity = 3 -> null - description = "access-ristrict-sample2-ristrict-waf-rulegp" -> null - id = "xxxxxxxxxx" -> null - lock_token = "5d8972fb-fe2e-4a69-8c8e-e6798b742207" -> null - name = "access-ristrict-sample2-ristrict-waf-rulegp" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = {} -> null - rule { - name = "access-ristrict-sample2-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample2/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/ipset/access-ristrict-sample2-ristrict-ipsets/xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample2-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample2-ristrict-rulegp" -> null - sampled_requests_enabled = true -> null } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"] will be destroyed - resource "aws_wafv2_rule_group" "access_ristrict" { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/rulegroup/access-ristrict-sample3-ristrict-waf-rulegp/xxxxxxxxxx" -> null - capacity = 3 -> null - description = "access-ristrict-sample3-ristrict-waf-rulegp" -> null - id = "xxxxxxxxxx" -> null - lock_token = "801a03dd-4216-4e23-8020-013a2defbccb" -> null - name = "access-ristrict-sample3-ristrict-waf-rulegp" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = {} -> null - rule { - name = "access-ristrict-sample3-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample3/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/ipset/access-ristrict-sample3-ristrict-ipsets/xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample3-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample3-ristrict-rulegp" -> null - sampled_requests_enabled = true -> null } } # module.waf.aws_wafv2_web_acl.web_acl will be destroyed - resource "aws_wafv2_web_acl" "web_acl" { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx" -> null - capacity = 6 -> null - description = "alb-wabacl" -> null - id = "xxxxxxxxxx" -> null - lock_token = "83122676-7566-4475-af9a-94ccbdbc8931" -> null - name = "alb-webacl" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "alb-webacl" } -> null - tags_all = { - "Name" = "alb-webacl" } -> null - token_domains = [] -> null - default_action { - allow { } } - rule { - name = "Access-Ristrict-Sample2PathIpRestriction" -> null - priority = 1001 -> null - override_action { - none {} } - statement { - rule_group_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/rulegroup/access-ristrict-sample2-ristrict-waf-rulegp/xxxxxxxxxx" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "Access-Ristrict-Sample2PathIpRestriction" -> null - sampled_requests_enabled = true -> null } } - rule { - name = "Access-Ristrict-Sample3PathIpRestriction" -> null - priority = 1002 -> null - override_action { - none {} } - statement { - rule_group_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/rulegroup/access-ristrict-sample3-ristrict-waf-rulegp/xxxxxxxxxx" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "Access-Ristrict-Sample3PathIpRestriction" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "alb-webacl" -> null - sampled_requests_enabled = true -> null } } # module.waf.aws_wafv2_web_acl_association.waf_association will be destroyed - resource "aws_wafv2_web_acl_association" "waf_association" { - id = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx" -> null - resource_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx" -> null - web_acl_arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx" -> null } # module.waf.aws_wafv2_web_acl_logging_configuration.waflog will be destroyed - resource "aws_wafv2_web_acl_logging_configuration" "waflog" { - id = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx" -> null - log_destination_configs = [ - "arn:aws:logs:ap-northeast-1:スイッチ先AWSアカウントID:log-group:aws-waf-logs-alb-webacl", ] -> null - resource_arn = "arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx" -> null - logging_filter { - default_behavior = "DROP" -> null - filter { - behavior = "KEEP" -> null - requirement = "MEETS_ANY" -> null - condition { - action_condition { - action = "BLOCK" -> null } } - condition { - action_condition { - action = "CAPTCHA" -> null } } - condition { - action_condition { - action = "COUNT" -> null } } } } } Plan: 0 to add, 0 to change, 8 to destroy. Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes module.waf.aws_wafv2_web_acl_association.waf_association: Destroying... [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:スイッチ先AWSアカウントID:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Destroying... [id=arn:aws:wafv2:ap-northeast-1:スイッチ先AWSアカウントID:regional/webacl/alb-webacl/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Destruction complete after 0s module.waf.aws_cloudwatch_log_group.cwlogs: Destroying... [id=aws-waf-logs-alb-webacl] module.waf.aws_cloudwatch_log_group.cwlogs: Destruction complete after 0s module.waf.aws_wafv2_web_acl_association.waf_association: Destruction complete after 2s module.waf.aws_wafv2_web_acl.web_acl: Destroying... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Destruction complete after 1s module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Destroying... [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Destroying... [id=xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Destruction complete after 1s module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Destruction complete after 1s module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Destroying... [id=xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Destroying... [id=xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Destruction complete after 0s module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Destruction complete after 0s Destroy complete! Resources: 8 destroyed. PS C:\Users\ユーザー\Documents\Terraform\terraform-dynamic-aws-waf-rules\envs\dev-waf>
おわりに
私の端末は Windows 11 であり AWSume を使いたくても、まずインストールのための pipx
や pip
を使うのが面倒!と思っていたので、朗報でした。
色々試行錯誤中ですが、一旦は今の環境でやりたいことができるようにセットアップできました。
ここまで設定しておいてアレですが、WSL で開発環境を Linux に寄せてしまう方が幸せになれるような気もしています。
この記事が Windows ユーザーの皆様のお役に立てば幸いです。
参考
脚注
- 私は Git for Windows に梱包されている Git Bash(MINGW64)で tfenv をインストールし任意のバージョンの Terraform をインストールしてから無理やり Windows の環境変数で terraform.exe へのパスを通す(terraform.exe は "C:\Users\ユーザー\.tfenv\versions\<任意のバージョン>" 配下にありました)という力業を実施しています。もっとスマートな方法があるのでは?と思っています。 ↩
- 追記:弊社メンバーが Windows 用の tfenv を作成しているので参考情報として記載します。PowerShell版の tfenv(のようなもの) をつくってみた | DevelopersIO https://dev.classmethod.jp/articles/i-published-powrshell-module-tfalias-like-tfenv/ ↩